﻿namespace NTLib.Pipe
{
    using NTLib.Pipe.Exceptions;
    using NTLib.Pipe.Providers;
    using NTLib.Pipe.Targets;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using NTLib.Core.Tasks;
    using NTLib.Core.Threading;
    using NTLib.Pipe.Descriptors;
    using NTLib.Core.Component;
    using Core.Exceptions;
    using Core.Resources;
    using Core.Extensions;

    /// <summary>
    /// This class is the main door to access all the pipe system.
    /// </summary>
    public class PipeManager : IPipeManager
    {
        #region Fields

        private static PipeManager s_instance;
        private readonly Dictionary<Guid, IPipeProvider> _allProviders;
        private readonly Dictionary<Guid, IPipeProvider> _pipeProvide;
        private readonly AsyncLock _lockerProvider;
        private readonly AsyncLock _lockerPipeAction;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes the <see cref="PipeManager"/> class.
        /// </summary>
        public PipeManager()
        {
            if (s_instance != null)
            {
                throw new SingletonException(StandardErrorMessages.OnlyOneInstanceAtTheTime.Argument(typeof(PipeManager)));
            }
            s_instance = this;
            _allProviders = new Dictionary<Guid, IPipeProvider>();
            _pipeProvide = new Dictionary<Guid, IPipeProvider>();
            _lockerProvider = new AsyncLock();
            _lockerPipeAction = new AsyncLock();
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="PipeManager"/> class.
        /// </summary>
        ~PipeManager()
        {
            ReleaseSingletonInstance();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the current singleton instance.
        /// </summary>
        public static PipeManager Instance
        {
            get { return s_instance; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates asynchronously the pipe in function of the target.
        /// </summary>
        /// <typeparam name="T">Type of the object that will travel through the pipe.</typeparam>
        /// <param name="type">The type.</param>
        /// <param name="target">The target description used to construct properly the pipe.</param>
        /// <returns>
        /// Pipe object used to transfert data.
        /// </returns>
        /// <exception cref="TargetNotFoundException">Raised if we couldn't reach the target.</exception>
        /// <exception cref="NTLib.Pipe.Exceptions.NoProviderRegisterException">Raised if no provider can managed the specific kind of target.</exception>
        public async Task<IPipeManipulatorOwner<T>> CreateAsync<T>(ITargetDescriptor target, PipeType type = PipeType.OneWay, CancellationToken token = default(CancellationToken))
        {
            IPipeProvider provider = null;

            Type descriptionType = (target != null) ? target.GetType() : null;

            using (var readItemLock = await _lockerProvider.LockAsync(token))
            {
                if (_allProviders.Count == 0)
                    throw new NoProviderRegisterException();

                foreach (var p in _allProviders)
                {
                    if (await p.Value.CanManagedTargetAsync(target, token))
                    {
                        provider = p.Value;
                        break;
                    }
                }
            }

            if (provider != null)
            {
                using (var writerItemLock = await _lockerPipeAction.LockAsync(token))
                {
                    var pipe = await provider.CreateAsync<T>(type, target);
                    _pipeProvide.Add(pipe.Id, provider);
                    return pipe;
                }
            }

            throw new TargetNotFoundException(target);
        }

        /// <summary>
        /// Changes the target of a pipe.
        /// </summary>
        /// <typeparam name="T">Pipe type object that will travel through it.</typeparam>
        /// <param name="pipe">The pipe to change the target.</param>
        /// <param name="newTarget">The new target.</param>
        /// <returns></returns>
        public Task ChangeTarget<T>(IPipe<T> pipe, ITargetDescriptor newTarget)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets the other side of the pipe from the pipe ID.
        /// </summary>
        /// <typeparam name="T">Type object that will travel through the pipe</typeparam>
        /// <param name="pipeId">The pipe identifier.</param>
        /// <returns>Return asynchrounously the other side of the pipe from the pipe ID</returns>
        /// <exception cref="NTLib.Pipe.Exceptions.NoProviderRegisterException">Raised if no provider have a pipe for this Guid.</exception>
        public async Task<IPipeManipulator<T>> GetPipeAsync<T>(Guid pipeId)
        {
            IPipeProvider provider = null;
            using (var readItemLock = await _lockerPipeAction.LockAsync())
            {
                _pipeProvide.TryGetValue(pipeId, out provider);
            }

            if (provider == null)
            {
                using (var readItemLock = await _lockerProvider.LockAsync())
                {
                    foreach (var prov in _allProviders)
                    {
                        if (await prov.Value.CanGetPipeAsync(pipeId))
                        {
                            provider = prov.Value;
                            break;
                        }
                    }
                }
            }

            if (provider == null)
                throw new NoProviderRegisterException();

            return await provider.GetPipeAsync<T>(pipeId);
        }

        /// <summary>
        /// Registers the specified provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public Guid Register<U>() where U : IPipeProvider
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Registers the specified provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public Guid Register(Type type)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Register a routing ruler to extend the PipeManager.
        /// </summary>
        /// <param name="provider"></param>
        public Guid Register(IPipeProvider provider)
        {
            using (var readItemLock = _lockerProvider.LockAsync().WaitResult())
            {
                if (!_allProviders.ContainsKey(provider.Id))
                    _allProviders.Add(provider.Id, provider);
                else
                    _allProviders[provider.Id] = provider;
            }
            return provider.Id;
        }

        /// <summary>
        /// Un register a routing ruler to extend the PipeManager.
        /// </summary>
        /// <param name="provider"></param>
        public void UnRegister<U>()
            where U : IPipeProvider
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Registers the specified provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public void UnRegister(Type type)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Un register a routing ruler to extend the PipeManager.
        /// </summary>
        /// <param name="provider"></param>
        public void UnRegister(IPipeProvider prov)
        {
            this.UnRegister(prov.Id);
        }

        /// <summary>
        /// Un register a routing ruler to extend the PipeManager.
        /// </summary>
        /// <param name="provider"></param>
        public void UnRegister(Guid id)
        {
            using (var readItemLock = _lockerProvider.LockAsync().WaitResult())
            {
                if (_allProviders.ContainsKey(id))
                    _allProviders.Remove(id);
            }
        }

        /// <summary>
        /// Releases the singleton instance.
        /// </summary>
        protected void ReleaseSingletonInstance()
        {
            s_instance = null;
        }


        #endregion
    }
}
